﻿using System;
using System.Threading;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;

namespace block_explorer
{
    class CubeMesher
    {
        private IMeshableCube mCube;
        private VertexBuffer mVertexBuffer;
        private int mTriCount;

        public int TriCount { get { return mTriCount; } }

        public CubeMesher(IMeshableCube cube, GraphicsDevice device)
        {
            mCube = cube;
            BuildMesh(device);
        }

        public VertexBuffer GetVertexBuffer()
        {
            return mVertexBuffer;
        }

        private void BuildMesh(GraphicsDevice device)
        {
            PerformanceTimer timer = new PerformanceTimer();

            int s = mCube.Size;
            float bs = mCube.BlockSize;
            MapSectorVertex[] tempVerts = new MapSectorVertex[s * s * (s + 1) * 3 * 2];
            int vertCount = 0;

            // create the vertex list for the whole sector
            for (int x = 0; x < s; ++x)
            {
                for (int y = 0; y < s; ++y)
                {
                    for (int z = 0; z < s; ++z)
                    {
                        Vector3 localPosition = new Vector3(x, y, z);
                        if (mCube.IsSolidBlockAt(localPosition))
                            DrawBoxAt(localPosition, bs, ref tempVerts, ref vertCount);
                    }
                }
                Thread.Sleep(0);
            }

            lock (this)
            {
                // create the vertex buffer
                if (vertCount > 0)
                {
                    mVertexBuffer = new VertexBuffer(device, MapSectorVertex.VertexDeclaration, vertCount, BufferUsage.WriteOnly);
                    MapSectorVertex[] verts = new MapSectorVertex[vertCount];
                    for (int i = 0; i < vertCount; ++i)
                        verts[i] = tempVerts[i];
                    mVertexBuffer.SetData(verts);
                    mTriCount = vertCount / 3;
                }
            }
        }

        private void DrawBoxAt(Vector3 localGridPosition, float blockSize, ref MapSectorVertex[] tempVerts, ref int vertexCount)
        {
            Vector3 position = localGridPosition * blockSize;
            Vector3 neighborPos;
            Block.ContentType content = mCube.GetBlockAt(localGridPosition).Content;
            Block neighbor;

            Vector2 texOffs;
            Vector2 texSize;
            Block.GetContentTextureOffsetSize(content, out texOffs, out texSize);

            neighborPos = localGridPosition + new Vector3(0, 0, 1);
            neighbor = mCube.GetBlockAt(neighborPos);
            if (neighbor != null && !neighbor.IsSolid())
                DrawFrontOfBox(position, blockSize, GetColorFromLightLevel(neighbor.LightValue), texOffs, texSize, ref tempVerts, ref vertexCount);

            neighborPos = localGridPosition + new Vector3(0, 0, -1);
            neighbor = mCube.GetBlockAt(neighborPos);
            if (neighbor != null && !neighbor.IsSolid())
                DrawBackOfBox(position, blockSize, GetColorFromLightLevel(neighbor.LightValue), texOffs, texSize, ref tempVerts, ref vertexCount);

            neighborPos = localGridPosition + new Vector3(1, 0, 0);
            neighbor = mCube.GetBlockAt(neighborPos);
            if (neighbor != null && !neighbor.IsSolid())
                DrawLeftOfBox(position, blockSize, GetColorFromLightLevel(neighbor.LightValue), texOffs, texSize, ref tempVerts, ref vertexCount);

            neighborPos = localGridPosition + new Vector3(-1, 0, 0);
            neighbor = mCube.GetBlockAt(neighborPos);
            if (neighbor != null && !neighbor.IsSolid())
                DrawRightOfBox(position, blockSize, GetColorFromLightLevel(neighbor.LightValue), texOffs, texSize, ref tempVerts, ref vertexCount);

            neighborPos = localGridPosition + new Vector3(0, 1, 0);
            neighbor = mCube.GetBlockAt(neighborPos);
            if (neighbor != null && !neighbor.IsSolid())
                DrawTopOfBox(position, blockSize, GetColorFromLightLevel(neighbor.LightValue), texOffs, texSize, ref tempVerts, ref vertexCount);

            neighborPos = localGridPosition + new Vector3(0, -1, 0);
            neighbor = mCube.GetBlockAt(neighborPos);
            if (neighbor != null && !neighbor.IsSolid())
                DrawBottomOfBox(position, blockSize, GetColorFromLightLevel(neighbor.LightValue), texOffs, texSize, ref tempVerts, ref vertexCount);
        }

        private void DrawFrontOfBox(Vector3 position,
                                    float blockSize,
                                    Color color,
                                    Vector2 texOffs,
                                    Vector2 texSize,
                                    ref MapSectorVertex[] tempVerts,
                                    ref int vertexCount)
        {
            Vector2 topLeft = texOffs;
            Vector2 botRight = topLeft + texSize;
            Vector3 normal = new Vector3(0, 0, 1);

            // |\
            // |_\
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z + blockSize), normal, new Vector2(botRight.X, botRight.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z + blockSize), normal, new Vector2(topLeft.X, botRight.Y), color));

            //  _
            // \ |
            //  \|
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(botRight.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z + blockSize), normal, new Vector2(botRight.X, botRight.Y), color));
        }

        private void DrawBackOfBox(Vector3 position,
                                   float blockSize,
                                   Color color,
                                   Vector2 texOffs,
                                   Vector2 texSize,
                                   ref MapSectorVertex[] tempVerts,
                                   ref int vertexCount)
        {
            Vector2 topLeft = texOffs;
            Vector2 botRight = topLeft + texSize;
            Vector3 normal = new Vector3(0, 0, -1);

            // |\
            // |_\
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z), normal, new Vector2(botRight.X, botRight.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z), normal, new Vector2(topLeft.X, botRight.Y), color));
            //  _
            // \ |
            //  \|
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z), normal, new Vector2(botRight.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z), normal, new Vector2(botRight.X, botRight.Y), color));
        }

        private void DrawLeftOfBox(Vector3 position, 
                                   float blockSize,
                                   Color color,
                                   Vector2 texOffs,
                                   Vector2 texSize,
                                   ref MapSectorVertex[] tempVerts,
                                   ref int vertexCount)
        {
            Vector2 topLeft = texOffs;
            Vector2 botRight = topLeft + texSize;
            Vector3 normal = new Vector3(1, 0, 0);

            // |\
            // |_\
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z), normal, new Vector2(botRight.X, botRight.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z + blockSize), normal, new Vector2(topLeft.X, botRight.Y), color));
            //  _
            // \ |
            //  \|
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z), normal, new Vector2(botRight.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z), normal, new Vector2(botRight.X, botRight.Y), color));
        }

        private void DrawRightOfBox(Vector3 position,
                                    float blockSize,
                                    Color color,
                                    Vector2 texOffs,
                                    Vector2 texSize,
                                    ref MapSectorVertex[] tempVerts,
                                    ref int vertexCount)
        {
            Vector2 topLeft = texOffs;
            Vector2 botRight = topLeft + texSize;
            Vector3 normal = new Vector3(-1, 0, 0);

            // |\
            // |_\
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z + blockSize), normal, new Vector2(botRight.X, botRight.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z), normal, new Vector2(topLeft.X, botRight.Y), color));
            //  _
            // \ |
            //  \|
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(botRight.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z + blockSize), normal, new Vector2(botRight.X, botRight.Y), color));
        }

        private void DrawTopOfBox(Vector3 position,
                                  float blockSize,
                                  Color color,
                                  Vector2 texOffs,
                                  Vector2 texSize,
                                  ref MapSectorVertex[] tempVerts,
                                  ref int vertexCount)
        {
            Vector2 topLeft = texOffs;
            Vector2 botRight = topLeft + texSize;
            Vector3 normal = new Vector3(0, 1, 0);

            // |\
            // |_\
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(botRight.X, botRight.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(topLeft.X, botRight.Y), color));
            //  _
            // \ |
            //  \|
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y + blockSize, position.Z), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z), normal, new Vector2(botRight.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y + blockSize, position.Z + blockSize), normal, new Vector2(botRight.X, botRight.Y), color));
        }

        private void DrawBottomOfBox(Vector3 position,
                                     float blockSize,
                                     Color color,
                                     Vector2 texOffs,
                                     Vector2 texSize,
                                     ref MapSectorVertex[] tempVerts,
                                     ref int vertexCount)
        {
            Vector2 topLeft = texOffs;
            Vector2 botRight = topLeft + texSize;
            Vector3 normal = new Vector3(0, -1, 0);

            // |\
            // |_\
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z + blockSize), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z), normal, new Vector2(botRight.X, botRight.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z), normal, new Vector2(topLeft.X, botRight.Y), color));
            //  _
            // \ |
            //  \|
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X, position.Y, position.Z + blockSize), normal, new Vector2(topLeft.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z + blockSize), normal, new Vector2(botRight.X, topLeft.Y), color));
            tempVerts[vertexCount++] = (new MapSectorVertex(new Vector3(position.X + blockSize, position.Y, position.Z), normal, new Vector2(botRight.X, botRight.Y), color));
        }

        private Color GetColorFromLightLevel(float level)
        {
            float baseColor = 0.086f;
            float colorValue = (float)(Math.Pow(level / 16.0f, 1.4f) + baseColor);

            int l = (int)(256.0f * colorValue);
            return new Color(l, l, l);
        }
    }
}
